//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_ATOMIC_BASE_H
#	define NJ_ATOMIC_BASE_H

#	include "NjNinjaSyncLib.h"
#	include "NjAssert_config.h"
#	include "NjFundamentalTypes.h"
#	include "NjFundamentalMacros.h"
#	include "NjConfiguration.h"

	/// \brief Collects atomic operations on types of size \em iSize
	/// \param iSize the size of the type on which operate atomically
	template <NjInt iSize>
	class NjAtomicBase;

	/// \brief Implementation of NjAtomicBase for types 4 bytes long
	template <>
	class NjAtomicBase<4>
	{
	protected:
		/// \brief Number type matches the size of this implementation
		typedef NjInt32 Number;

		/// \brief Default constructor for NjAtomicBase
		///
		/// \em m_iNumber is left uninitialized
		NjAtomicBase();

		/// \brief Copy constructor for NjAtomicBase
		/// \param kCopy the attribute \em m_iNumber is initialized to the value in \em kCopy
		NjAtomicBase(NjAtomicBase const & kCopy);

		/// \brief Creates an NjAtomicBase and initializes it to \em iCopy
		/// \param iCopy the attribute \em m_iNumber is initialized to value \em iCopy
		NjAtomicBase(Number iCopy);

		/// \brief Destructor for NjAtomicBase
		~NjAtomicBase();

		/// \brief Atomically increments \em m_iNumber by one
		///
		/// It uses the most performant between PreIncrement and PostIncrement and does not return any value
		void Increment();

		/// \brief Atomically increments \em m_iNumber by one
		/// \return \em m_iNumber value after this operation
		Number PreIncrement();

		/// \brief Atomically increments \em m_iNumber by one
		/// \return \em m_iNumber value before this operation
		Number PostIncrement();

		/// \brief Atomically decrements \em m_iNumber by one
		///
		/// It uses the most performant between PreDecrement and PostDecrement and does not return any value
		void Decrement();

		/// \brief Atomically decrements \em m_iNumber by one
		/// \return \em m_iNumber value after this operation
		Number PreDecrement();

		/// \brief Atomically decrements \em m_iNumber by one
		/// \return \em m_iNumber value before this operation
		Number PostDecrement();

		/// \brief Atomically adds \em iRightValue to \em m_iNumber
		///
		/// It uses the most performant between PreAdd and PostAdd and does not return any value
		/// \param iRightValue the value to be added to \em m_iNumber
		void Add(Number iRightValue);

		/// \brief Atomically adds \em iRightValue to \em m_iNumber
		/// \param iRightValue the value to be added to \em m_iNumber
		/// \return \em m_iNumber value after this operation
		Number PreAdd(Number iRightValue);

		/// \brief Atomically adds \em iRightValue to \em m_iNumber
		/// \param iRightValue the value to be added to \em m_iNumber
		/// \return \em m_iNumber value before this operation
		Number PostAdd(Number iRightValue);

		/// \brief Atomically subtracts \em iRightValue from \em m_iNumber
		///
		/// It uses the most performant between PreSub and PostSub and does not return any value
		/// \param iRightValue the value to be subtracted from \em m_iNumber
		void Sub(Number iRightValue);

		/// \brief Atomically subtracts \em iRightValue from \em m_iNumber
		/// \param iRightValue the value to be subtracted from \em m_iNumber
		/// \return \em m_iNumber value after this operation
		Number PreSub(Number iRightValue);

		/// \brief Atomically subtracts \em iRightValue from \em m_iNumber
		/// \param iRightValue the value to be subtracted from \em m_iNumber
		/// \return \em m_iNumber value before this operation
		Number PostSub(Number iRightValue);

		/// \brief Atomically updates \em m_iNumber making a logical or with \em iRightValue
		/// \param iRightValue the value used in the logical or with \em m_iNumber
		/// \return \em m_iNumber value before this operation
		Number PostOr(Number iRightValue);

		/// \brief Atomically updates \em iRightValue making a logical and with \em m_iNumber
		/// \param iRightValue the value used in the logical and with \em m_iNumber
		/// \return \em m_iNumber value before this operation
		Number PostAnd(Number iRightValue);

		/// \brief Atomically updates \em iRightValue making a logical xor with \em m_iNumber
		/// \param iRightValue the value used in the logical xor with \em m_iNumber
		/// \return \em m_iNumber value before this operation
		Number PostXor(Number iRightValue);

		/// \brief Atomically updates \em m_iNumber to \em iValue
		/// \param iValue the value copied into \em m_iNumber
		/// \return The value od \em m_iNumber before this operation
		Number PostExchange(Number iValue);

		/// \brief Atomically compares \em m_iNumber with \em iComparand and if they are equal updates it to \em iValue
		/// \param iComparand the value to be used in the comparison operation with \em m_iNumber
		/// \param iValue the value copied into \em m_iNumber if the comparison is successful
		/// \return The value od \em m_iNumber before this operation
		Number PostCompareExchange(Number iComparand, Number iValue);

		/// \brief Set the \em iOffset th bit of \em m_iNumber to 1
		/// \return The previous bit state as NJ_TRUE or NJ_FALSE
		NjBool PostSetBit(Number iOffset);

		/// \brief Set the \em iOffset th bit of \em m_iNumber to 0
		/// \return The previous bit state as NJ_TRUE or NJ_FALSE
		NjBool PostResetBit(Number iOffset);

		/// \brief Returns the value of \em m_iNumber
		Number Get() const;

		/// \brief Updates \em m_iNumber to \em iValue
		/// \param iValue the value copied into \em m_iNumber
		void Set(Number iValue);

	protected:
#	if NJ_ASSERT_ENABLED
		/// \brief Ensures (through assertion) that the alignment condition is satisfied
		void TestAlignment();
#	endif // NJ_ASSERT_ENABLED

		/// \brief The underlying value that is updated atomically
		volatile NJ_ALIGN(4) Number m_iNumber;

		NJ_DISABLE_ASSIGNMENT_OPERATOR(NjAtomicBase);
	};

	/// \brief Implementation of NjAtomicBase for types 8 bytes long
	template <>
	class NjAtomicBase<8>
	{
	protected:
		/// \copydoc NjAtomicBase<4>::Number
		typedef NjInt64 Number;

		/// \copydoc NjAtomicBase<4>::NjAtomicBase()
		NjAtomicBase();

		/// \copydoc NjAtomicBase<4>::NjAtomicBase(NjAtomicBase const &)
		NjAtomicBase(NjAtomicBase const & kCopy);

		/// \copydoc NjAtomicBase<4>::NjAtomicBase(Number)
		NjAtomicBase(Number iCopy);

		/// \copydoc NjAtomicBase<4>::~NjAtomicBase
		~NjAtomicBase();

		/// \copydoc NjAtomicBase<4>::Increment
		void Increment();

		/// \copydoc NjAtomicBase<4>::PreIncrement
		Number PreIncrement();

		/// \copydoc NjAtomicBase<4>::PostIncrement
		Number PostIncrement();

		/// \copydoc NjAtomicBase<4>::Decrement
		void Decrement();

		/// \copydoc NjAtomicBase<4>::PreDecrement
		Number PreDecrement();

		/// \copydoc NjAtomicBase<4>::PostDecrement
		Number PostDecrement();

		/// \copydoc NjAtomicBase<4>::Add
		void Add(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PreAdd
		Number PreAdd(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PostAdd
		Number PostAdd(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::Sub
		void Sub(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PreSub
		Number PreSub(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PostSub
		Number PostSub(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PostOr
		Number PostOr(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PostAnd
		Number PostAnd(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PostXor
		Number PostXor(Number iRightValue);

		/// \copydoc NjAtomicBase<4>::PostExchange
		Number PostExchange(Number iValue);

		/// \copydoc NjAtomicBase<4>::PostCompareExchange
		Number PostCompareExchange(Number iComparand, Number iValue);

		/// \copydoc NjAtomicBase<4>::PostSetBit
		NjBool PostSetBit(Number iBit);

		/// \copydoc NjAtomicBase<4>::PostResetBit
		NjBool PostResetBit(Number iBit);

		/// \copydoc NjAtomicBase<4>::Get
		Number Get() const;

		/// \copydoc NjAtomicBase<4>::Set
		void Set(Number iValue);

	private:
#	if NJ_ASSERT_ENABLED
		/// \copydoc NjAtomicBase<4>::TestAlignment
		void TestAlignment();
#	endif // NJ_ASSERT_ENABLED

		/// \copydoc NjAtomicBase<4>::m_iNumber
		volatile NJ_ALIGN(8) Number m_iNumber;

		NJ_DISABLE_ASSIGNMENT_OPERATOR(NjAtomicBase);
	};

	/// \brief Implementation of NjAtomicBase for types 1 bytes long
	template <>
	class NjAtomicBase<1> : public NjAtomicBase<4>
	{
	protected:
		/// \copydoc NjAtomicBase<4>::NjAtomicBase()
		NjAtomicBase();

		/// \copydoc NjAtomicBase<4>::NjAtomicBase(NjAtomicBase const &)
		NjAtomicBase(NjAtomicBase const & kCopy);

		/// \copydoc NjAtomicBase<4>::NjAtomicBase(Number)
		NjAtomicBase(Number iCopy);

		/// \copydoc NjAtomicBase<4>::~NjAtomicBase
		~NjAtomicBase();

		/// \copydoc NjAtomicBase<4>::PostCompareExchange
		Number PostCompareExchange(Number iComparand, Number iValue);

	private:
		NJ_DISABLE_ASSIGNMENT_OPERATOR(NjAtomicBase);
	};

	/// \brief Implementation of NjAtomicBase for types 2 bytes long
	template <>
	class NjAtomicBase<2> : public NjAtomicBase<4>
	{
	protected:
		/// \copydoc NjAtomicBase<4>::NjAtomicBase()
		NjAtomicBase();

		/// \copydoc NjAtomicBase<4>::NjAtomicBase(NjAtomicBase const &)
		NjAtomicBase(NjAtomicBase const & kCopy);

		/// \copydoc NjAtomicBase<4>::NjAtomicBase(Number)
		NjAtomicBase(Number iCopy);

		/// \copydoc NjAtomicBase<4>::~NjAtomicBase
		~NjAtomicBase();

		/// \copydoc NjAtomicBase<4>::PostCompareExchange
		Number PostCompareExchange(Number iComparand, Number iValue);

	private:
		NJ_DISABLE_ASSIGNMENT_OPERATOR(NjAtomicBase);
	};

#	include "NjAtomicBase_inline.h"

#	if NJ_PLATFORM_OS_WIN
#		include "NjAtomicBaseWin_inline.h"
#	elif NJ_PLATFORM_OS_LINUX
#		include "NjAtomicBaseLinux_inline.h"
#	else // NJ_PLATFORM_OS_WIN || NJ_PLATFORM_OS_LINUX
#		error "Unsupported platform configuration"
#	endif // NJ_PLATFORM_OS_WIN || NJ_PLATFORM_OS_LINUX

#endif // NJ_ATOMIC_BASE_H
